<?php
/**
 * Author: Dcr
 * Date: 2019/7/18
 * Time: 15:14
 */
namespace dcr\classes;
defined('IN_DCR') or exit('No permission resources.');

class PdoClass
{
    static $instance = null;
    protected $pdo;
    static protected $cfg;
    protected $tablepre;
    protected $result;
    protected $statement;
    protected $errors = array();
    protected $link = array();
    private function __construct()
    {

        $this->connect();
    }
    static function getInstance($config){
        if( is_null(self::$instance) ){
            static::$cfg = $config;
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function connect(){
        $config = self::$cfg;
        $this->tablepre = $config['table_prefix']; //表前缀
        try{
            //链接数据库
            $this->pdo = new \PDO('mysql:host='.$config['host'].':'.$config['port'].';dbname='.$config['database'],$config['username'],$config['password']);
            //设置字符集
            $this->pdo->exec('SET NAMES '.$config['charset']);
        }catch(\PDOException $e){
            throw new \Exception( 'Connection failed: ' . $e->getMessage() );
        }
        return $this;
    }

    public function prepare($sql) {
        $statement = $this->pdo->prepare($sql);
        return $statement;
    }


    public function query($sql, $params = array()) {
        $starttime = microtime();
        if (empty($params)) {
            $result = $this->pdo->exec($sql);
            if(APP_DEBUG) {
                $info = array();
                $info['sql'] = $sql;
                $info['error'] = $this->pdo->errorInfo();
                $this->debug(false, $info);
            }
            return $result;
        }
        $statement = $this->prepare($sql);
        $result = $statement->execute($params);
        if(APP_DEBUG) {
            $info = array();
            $info['sql'] = $sql;
            $info['params'] = $params;
            $info['error'] = $statement->errorInfo();
            $this->debug(false, $info);
        }
        $endtime = microtime();

        if (!$result) {
            return false;
        } else {
            return $statement->rowCount();
        }
    }


    public function fetchcolumn($sql, $params = array(), $column = 0) {
        $starttime = microtime();
        $statement = $this->prepare($sql);
        $result = $statement->execute($params);
        if(APP_DEBUG) {
            $info = array();
            $info['sql'] = $sql;
            $info['params'] = $params;
            $info['error'] = $statement->errorInfo();
            $this->debug(false, $info);
        }
        $endtime = microtime();

        if (!$result) {
            return false;
        } else {
            return $statement->fetchColumn($column);
        }
    }


    public function fetch($sql, $params = array()) {
        $starttime = microtime();
        $statement = $this->prepare($sql);
        $result = $statement->execute($params);
        if(APP_DEBUG) {
            $info = array();
            $info['sql'] = $sql;
            $info['params'] = $params;
            $info['error'] = $statement->errorInfo();
            $this->debug(false, $info);
        }
        $endtime = microtime();

        if (!$result) {
            return false;
        } else {
            return $statement->fetch(\PDO::FETCH_ASSOC);
        }
    }


    public function fetchall($sql, $params = array(), $keyfield = '') {
        $starttime = microtime();
        $statement = $this->prepare($sql);
        $result = $statement->execute($params);
        if(APP_DEBUG) {
            $info = array();
            $info['sql'] = $sql;
            $info['params'] = $params;
            $info['error'] = $statement->errorInfo();
            $this->debug(false, $info);
        }
        $endtime = microtime();

        if (!$result) {
            return false;
        } else {
            if (empty($keyfield)) {
                return $statement->fetchAll(\PDO::FETCH_ASSOC);
            } else {
                $temp = $statement->fetchAll(\PDO::FETCH_ASSOC);
                $rs = array();
                if (!empty($temp)) {
                    foreach ($temp as $key => &$row) {
                        if (isset($row[$keyfield])) {
                            $rs[$row[$keyfield]] = $row;
                        } else {
                            $rs[] = $row;
                        }
                    }
                }
                return $rs;
            }
        }
    }

    /**
     * @param $talename
     * @param string $filed
     * @param $params
     * @return mixed
     */
    public function count($talename,$filed = '*',$params=array()){
        $condition = $this->implode($params, 'AND');
        $sql = 'SELECT COUNT(\''.$filed.'\') AS count FROM '.$this->tablename($talename). (!empty($condition['fields']) ? " WHERE {$condition['fields']}" : '') . " LIMIT 1";  ;
        $res = $this->fetch($sql,$condition['params']);
        return intval($res['count']);
    }

    public function get($tablename, $params = array(), $fields = array()) {
        $select = '*';
        if (!empty($fields)){
            if (is_array($fields)) {
                $select = '`'.implode('`,`', $fields).'`';
            } else {
                $select = $fields;
            }
        }

        $condition = $this->implode($params, 'AND');
        $sql = "SELECT {$select} FROM " . $this->tablename($tablename) . (!empty($condition['fields']) ? " WHERE {$condition['fields']}" : '') . " LIMIT 1";
        return $this->fetch($sql, $condition['params']);
    }

    public function getall($tablename, $params = array(), $fields = array(), $keyfield = '') {
        $select = '*';
        if (!empty($fields)){
            if (is_array($fields)) {
                $select = '`'.implode('`,`', $fields).'`';
            } else {
                $select = $fields;
            }
        }
        $condition = $this->implode($params, 'AND');
        $sql = "SELECT {$select} FROM " .$this->tablename($tablename) . (!empty($condition['fields']) ? " WHERE {$condition['fields']}" : '') . $limitsql;
        return $this->fetchall($sql, $condition['params'], $keyfield);
    }

    public function getslice($tablename, $params = array(), $limit = array(), &$total = null, $fields = array(), $keyfield = '') {
        $select = '*';
        if (!empty($fields)){
            if (is_array($fields)) {
                $select = '`'.implode('`,`', $fields).'`';
            } else {
                $select = $fields;
            }
        }
        $condition = $this->implode($params, 'AND');
        if (!empty($limit)) {
            if (is_array($limit)) {
                $limitsql = " LIMIT " . ($limit[0] - 1) * $limit[1] . ', ' . $limit[1];
            } else {
                $limitsql = strexists(strtoupper($limit), 'LIMIT') ? " $limit " : " LIMIT $limit";
            }
        }
        $sql = "SELECT {$select} FROM " . $this->tablename($tablename) . (!empty($condition['fields']) ? " WHERE {$condition['fields']}" : '') . $limitsql;
        $total = pdo_fetchcolumn("SELECT COUNT(*) FROM " . tablename($tablename) . (!empty($condition['fields']) ? " WHERE {$condition['fields']}" : ''), $condition['params']);
        return $this->fetchall($sql, $condition['params'], $keyfield);
    }

    public function getcolumn($tablename, $params = array(), $field) {
        $result = $this->get($tablename, $params, array($field));
        if (!empty($result)) {
            return $result[$field];
        } else {
            return false;
        }
    }

    public function update($table, $data = array(), $params = array(), $glue = 'AND') {
        $fields = $this->implode($data, ',');
        $condition = $this->implode($params, $glue);
        $params = array_merge($fields['params'], $condition['params']);
        $sql = "UPDATE " . $this->tablename($table) . " SET {$fields['fields']}";
        $sql .= $condition['fields'] ? ' WHERE '.$condition['fields'] : '';
        return $this->query($sql, $params);
    }

    public function insert($table, $data = array(), $replace = FALSE) {
        $cmd = $replace ? 'REPLACE INTO' : 'INSERT INTO';
        $condition = $this->implode($data, ',');
        return $this->query("$cmd " . $this->tablename($table) . " SET {$condition['fields']}", $condition['params']);
    }

    public function insertid() {
        return $this->pdo->lastInsertId();
    }

    public function delete($table, $params = array(), $glue = 'AND') {
        $condition = $this->implode($params, $glue);
        $sql = "DELETE FROM " . $this->tablename($table);
        $sql .= $condition['fields'] ? ' WHERE '.$condition['fields'] : '';
        return $this->query($sql, $condition['params']);
    }

    public function begin() {
        $this->pdo->beginTransaction();
    }

    public function commit() {
        $this->pdo->commit();
    }

    public function rollback() {
        $this->pdo->rollBack();
    }

    private function implode($params, $glue = ',') {
        $result = array('fields' => ' 1 ', 'params' => array());
        $split = '';
        $suffix = '';
        $allow_operator = array('>', '<', '<>', '!=', '>=', '<=', '+=', '-=', 'LIKE', 'like');
        if (in_array(strtolower($glue), array('and', 'or'))) {
            $suffix = '__';
        }
        if (!is_array($params)) {
            $result['fields'] = $params;
            return $result;
        }
        if (is_array($params)) {
            $result['fields'] = '';
            foreach ($params as $fields => $value) {
                $operator = '';
                if (strpos($fields, ' ') !== FALSE) {
                    list($fields, $operator) = explode(' ', $fields, 2);
                    if (!in_array($operator, $allow_operator)) {
                        $operator = '';
                    }
                }
                if (empty($operator)) {
                    $fields = trim($fields);
                    if (is_array($value)) {
                        $operator = 'IN';
                    } else {
                        $operator = '=';
                    }
                } elseif ($operator == '+=') {
                    $operator = " = `$fields` + ";
                } elseif ($operator == '-=') {
                    $operator = " = `$fields` - ";
                }
                if (is_array($value)) {
                    $insql = array();
                    foreach ($value as $k => $v) {
                        $insql[] = ":{$suffix}{$fields}_{$k}";
                        $result['params'][":{$suffix}{$fields}_{$k}"] = is_null($v) ? '' : $v;
                    }
                    $result['fields'] .= $split . "`$fields` {$operator} (".implode(",", $insql).")";
                    $split = ' ' . $glue . ' ';
                } else {
                    $result['fields'] .= $split . "`$fields` {$operator}  :{$suffix}$fields";
                    $split = ' ' . $glue . ' ';
                    $result['params'][":{$suffix}$fields"] = is_null($value) ? '' : $value;
                }
            }
        }
        return $result;
    }


    public function run($sql, $stuff = 'ims_') {
        if(!isset($sql) || empty($sql)) return;

        $sql = str_replace("\r", "\n", str_replace(' ' . $stuff, ' ' . $this->tablepre, $sql));
        $sql = str_replace("\r", "\n", str_replace(' `' . $stuff, ' `' . $this->tablepre, $sql));
        $ret = array();
        $num = 0;
        $sql = preg_replace("/\;[ \f\t\v]+/", ';', $sql);
        foreach(explode(";\n", trim($sql)) as $query) {
            $ret[$num] = '';
            $queries = explode("\n", trim($query));
            foreach($queries as $query) {
                $ret[$num] .= (isset($query[0]) && $query[0] == '#') || (isset($query[1]) && isset($query[1]) && $query[0].$query[1] == '--') ? '' : $query;
            }
            $num++;
        }
        unset($sql);
        foreach($ret as $query) {
            $query = trim($query);
            if($query) {
                $this->query($query, array());
            }
        }
    }


    public function fieldexists($tablename, $fieldname) {
        $isexists = $this->fetch("DESCRIBE " . $this->tablename($tablename) . " `{$fieldname}`", array());
        return !empty($isexists) ? true : false;
    }


    public function indexexists($tablename, $indexname) {
        if (!empty($indexname)) {
            $indexs = $this->fetchall("SHOW INDEX FROM " . $this->tablename($tablename), array(), '');
            if (!empty($indexs) && is_array($indexs)) {
                foreach ($indexs as $row) {
                    if ($row['Key_name'] == $indexname) {
                        return true;
                    }
                }
            }
        }
        return false;
    }


    public function tablename($table) {
        return "`{$this->tablepre}{$table}`";
    }


    public function debug($output = true, $append = array()) {
        if(!empty($append)) {
            $output = false;
            array_push($this->errors, $append);
        }
        if($output) {
            print_r($this->errors);
        } else {
            if (!empty($append['error'][1])) {
                $traces = debug_backtrace();
                $ts = '';
                foreach($traces as $trace) {
                    $trace['file'] = str_replace('\\', '/', $trace['file']);
                    $trace['file'] = str_replace(IA_ROOT, '', $trace['file']);
                    $ts .= "file: {$trace['file']}; line: {$trace['line']}; <br />";
                }
                $params = var_export($append['params'], true);
                throw new \Exception("SQL: <br/>{$append['sql']}<hr/>Params: <br/>{$params}<hr/>SQL Error: <br/>{$append['error'][2]}<hr/>Traces: <br/>{$ts}");
            }
        }
        return $this->errors;
    }

    public function tableexists($table) {
        if(!empty($table)) {
            $data = $this->fetch("SHOW TABLES LIKE '{$this->tablepre}{$table}'", array());
            if(!empty($data)) {
                $data = array_values($data);
                $tablename = $this->tablepre . $table;
                if(in_array($tablename, $data)) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
}